<template>
  <div class="cont">
    <div class="record" ref="recordContainer">
      <div class="flex mb-16px">
        <el-avatar :icon="ChatSquare" />
        <div class="text">
          <div
            >Hi，我是 ICT政策智能问答助手～
            很高兴遇见你！作为你的智能伙伴,你可以随时问我有关ICT政策的知识。</div
          >
        </div>
        <el-avatar style="opacity: 0" :icon="UserFilled" />
      </div>
      <div v-for="(item, index) in messageList" :key="item">
        <div v-if="item.role !== 'user'" class="flex mb-16px">
          <el-avatar :icon="ChatSquare" />
          <div class="text" style="flex: 1 1">
            <el-collapse v-if="item.docs" class="mb-10px" :value="activeStates[index]">
              <el-collapse-item :name="'item-' + index">
                <template #title>
                  <el-icon class="header-icon mr-10px">
                    <Collection />
                  </el-icon>
                  知识库匹配结果
                </template>
                <div>
                  <div v-if="item.docs">
                    <div v-for="i in item.docs" :key="i" :v-html="i">
                      <p class="my-5px fw-550"
                        >{{ i.title }}
                        <a :href="i.link" target="_blank">{{ i.text }}</a>
                      </p>
                      <p class="my-1px">{{ i.content }} </p>
                    </div>
                  </div>
                  <div v-else class="text-#c00000">暂无匹配数据，基于大模型本身能力回答</div>
                </div>
              </el-collapse-item>
            </el-collapse>
            <div
              :class="{
                loading_style:
                  item.content === '思考中...' &&
                  conversationInProgress &&
                  index == messageList.length - 1
              }"
            >
              <div v-html="markdownIt.render(item.content)"></div>
              <div
                v-if="!conversationInProgress && index == messageList.length - 1"
                class="complete_style"
              >
                回答完成！
              </div>
            </div>
            <el-button
              v-if="conversationInProgress && index == messageList.length - 1"
              link
              type="primary"
              class="todo_center"
              @click="stopStream"
            >
              <div class="text-12px text-#409eff">停止回答</div>
            </el-button>
          </div>
          <el-avatar style="opacity: 0" :icon="UserFilled" />
        </div>
        <div v-if="item.role == 'user'" class="flex mb-16px justify-end">
          <el-avatar style="opacity: 0" :icon="UserFilled" />
          <div class="text mytext">
            {{ item.content }}
          </div>
          <el-avatar :icon="UserFilled" />
        </div>
      </div>
    </div>
    <div class="question">
      <el-input
        class="prompt-input"
        v-model="text"
        type="textarea"
        autocomplete="on"
        resize="none"
        :autosize="{ minRows: 2, maxRows: 4 }"
        placeholder="可以问我问题...（Shift+Enter 换行，按下 Enter 发送）"
        @keydown="handleKeydown"
      />
      <el-button
        class="mt-6px"
        :disabled="!hasText"
        type="primary"
        :icon="Promotion"
        circle
        @click="sendMessage(text)"
      />
    </div>
  </div>
</template>

<script lang="ts" setup>
import * as KmsApi from '@/api/project/kms'
import { UserFilled } from '@element-plus/icons-vue'
import { ChatSquare } from '@element-plus/icons-vue'
import { Collection } from '@element-plus/icons-vue'
import { Promotion } from '@element-plus/icons-vue'
import MarkdownIt from 'markdown-it'
const markdownIt = new MarkdownIt({
  html: true, // 允许HTML标签
  xhtmlOut: true, // 使用XHTML自闭合标签
  breaks: true, // 将换行符转换为<br>
  linkify: true, // 自动将URL转换为链接
  typographer: true // 启用一些排版相关的替换
})
const text = ref('')
const messageList = ref([]) // 对话数据
// const activeNames = ref(['1'])
const activeStates = ref({})
const conversationInAbortController = ref<any>() // 对话进行中 abort 控制器(控制 stream 对话)
const conversationInProgress = ref(false) // 对话是否正在进行中
// 接收 Stream 消息
const receiveMessageFullText = ref('')
const message = useMessage() // 消息弹窗
const docsList = ref([]) // docs数据
const hasText = computed(() => text.value.trim() !== '' && conversationInProgress.value == false)
const handleKeydown = (event) => {
  if (event.key === 'Enter' && !event.shiftKey) {
    event.preventDefault()
    sendMessage(text.value)
  }
}
const recordContainer = ref<HTMLElement | null>(null)

const sendMessage = async (content) => {
  if (hasText.value) {
    // 执行发送
    await doSendMessageStream({
      content: content
    })
    text.value = ''
    // 滚动到底部
    await nextTick()
    if (recordContainer.value) {
      recordContainer.value.scrollTop = recordContainer.value.scrollHeight
    }
  }
}

// 真正执行【发送】消息操作
const doSendMessageStream = async (userMessage) => {
  // 创建 AbortController 实例，以便中止请求
  const controller = new AbortController()
  conversationInAbortController.value = controller
  // 标记对话进行中
  conversationInProgress.value = true
  // 设置为空
  receiveMessageFullText.value = ''

  // 初始化发送列表
  let sendList = [{ role: 'user', content: userMessage.content }]

  // 如果消息列表中有超过一个元素，则添加最后两个消息到发送列表
  if (messageList.value.length > 1) {
    sendList = [...messageList.value.slice(-2), ...sendList]
  }

  // 添加临时消息到消息列表
  messageList.value.push(
    { role: 'user', content: userMessage.content },
    { role: 'assistant', content: '思考中...' }
  )
  // 定义发送消息的函数
  const sendMessageStream = async () => {
    try {
      const data = {
        path: {
          mode: 'local_kb',
          param: 'ict-system'
        },
        body: {
          model: 'qwen2.5:3b',
          messages: sendList,
          stream: true,
          temperature: 0.7,
          top_k: 3,
          score_threshold: 1.99,
          prompt_name: 'default',
          return_direct: false
        }
      }
      await KmsApi.sendChatMessageStream(
        data,
        controller,
        async (res) => {
          if (res.data) {
            const jsonString = res.data.split('data: ')[1] || '{}' // 防止解析错误
            const responseData = JSON.parse(jsonString)
            // const responseData = JSON.parse(res.data)
            if (responseData.docs) {
              // 处理数据
              docsList.value = responseData.docs
                .map((doc) => {
                  const match = doc.match(/^出处 \[(\d+)\] \[(.*?)\]\((.*?)\)/)
                  if (match) {
                    const [, title, text, link] = match
                    const contentStart = doc.indexOf('\n\n') + 2
                    const content = doc.slice(contentStart).trim()
                    return {
                      title: `出处 [${title}]`,
                      text,
                      link,
                      content
                    }
                  } else {
                    return null
                  }
                })
                .filter((item) => item !== null)
              // 更新消息列表
              messageList.value = messageList.value.slice(0, -2) // 移除最后两个临时消息
              messageList.value.push(
                { role: 'user', content: userMessage.content },
                {
                  role: 'assistant',
                  content: '思考中...',
                  docs: docsList.value
                }
              )
            }
            if (responseData.choices) {
              if (responseData.choices[0]?.delta?.content === '') {
                return
              }
              // 更新接收到的消息内容
              receiveMessageFullText.value += responseData.choices[0]?.delta?.content
              // 更新消息列表中的 content 字段
              messageList.value[messageList.value.length - 1].content = receiveMessageFullText.value
              // messageList.value[messageList.value.length].docs = docsList.value
              await nextTick()
              if (recordContainer.value) {
                recordContainer.value.scrollTop = recordContainer.value.scrollHeight
              }
            }
          }
        },
        (error) => {
          console.log(`对话异常! ${error}`)
          messageList.value = messageList.value.slice(0, -2) // 移除最后两个临时消息
          messageList.value.push(
            { role: 'user', content: userMessage.content },
            {
              role: 'assistant',
              content: `对话异常,请稍后重试!`
            }
          )
          stopStream()
          throw error
        },
        () => {
          stopStream()
        }
      )
    } catch (error) {
      console.error('发送消息时出错:', error)
      stopStream() // 确保在错误时也停止流
    }
  }

  // 调用发送消息的函数
  sendMessageStream()
}

// 停止 stream 流式调用
const stopStream = async () => {
  if (conversationInAbortController.value) {
    conversationInAbortController.value.abort()
  }
  // 对话设置为 false
  conversationInProgress.value = false
}
</script>
<style scoped>
.cont {
  display: flex;
  flex-direction: column;
  height: calc(100vh - 180px);
  width: 70%;
  margin: auto;
}

.record {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  padding-right: 10px;
}

.question {
  background-color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  border-radius: 16px;
  box-shadow: 0 -2px 6px #bcbcbca6;
  padding: 5px 12px 8px 16px;
}

.text {
  background-color: #fff;
  border: 1px solid transparent;
  border-radius: 4px 20px 20px 20px;
  box-sizing: border-box;
  color: #1e1f24;
  font-size: 16px;
  padding: 12px 19px;
  text-align: justify;
  margin: 0 16px;
  line-height: 25px;
}

.mytext {
  max-width: calc(100% - 112px);
  background: #409eff;
  border-radius: 20px;
  border-bottom-right-radius: 4px;
  box-sizing: border-box;
  color: #fafafa;
  font-size: 16px;
}

:deep(.el-textarea__inner),
.el-textarea__inner:hover {
  box-shadow: none;
}

:deep(.el-textarea__inner) {
  line-height: 28px;
}

:deep(.record)::-webkit-scrollbar {
  width: 6px;
}

:deep(.record):hover::-webkit-scrollbar-thumb {
  background: rgba(68, 68, 68, 0.1);
}

:deep(.record)::-webkit-scrollbar-thumb {
  border-radius: 20px;
  background: transparent;
}

:deep(.record)::-webkit-scrollbar-track {
  display: none;
}

:deep(.el-textarea__inner)::-webkit-scrollbar {
  width: 4px;
}

:deep(.el-textarea__inner)::-webkit-scrollbar-thumb {
  border-radius: 10px;
  background: rgba(68, 68, 68, 0.1);
}

:deep(.el-collapse-item__content) {
  font-size: 16px;
}
:deep(.el-collapse-item__header) {
  font-size: 16px;
}
:deep(.el-collapse-item__header.is-active) {
  color: #409eff;
}

.loading_style {
  font-size: 14px;
  color: #409eff;
  background-color: #22badd21;
  border-left: 3px solid #409eff;
}

.loading_style > div {
  animation: jump 1s infinite;
}

.complete_style {
  font-size: 14px;
  color: #409eff;
  background-color: #22badd21;
  border-left: 3px solid #409eff;
  padding-left: 10px;
  margin-top: 5px;
}

@keyframes jump {
  0%,
  100% {
    transform: translateX(10px) scale(1);
    opacity: 1;
  }
  50% {
    transform: translateX(60px) scale(1.1);
    opacity: 0.8;
  }
}
a {
  color: rgb(51.2, 126.4, 204);
  outline: none;
}
</style>